/*
Copyright (c) 2008, Mike Gashler

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/

#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <stdarg.h>
#ifdef WIN32
#	include <direct.h>
#	include <io.h>
#	include <windows.h>
#else
#	include <sys/stat.h>
#	include <signal.h>
#	include <sys/wait.h>
#endif // WIN32
#include "app.h"
#include "GHttp.h"
#include "GHtml.h"
#include "GSpinLock.h"
#include "GSocket.h"
#include <errno.h>
#include <time.h>

using namespace std;

#define APP_NAME "gpeerreview"

const char FIELD_REVIEWER_URL[] = "reviewer-url: ";
const char FIELD_FILENAME[] = "filename: ";
const char FIELD_REVIEWER_NAME[] = "reviewer-name: ";
const char FIELD_DATE[] = "date: ";
const char ARMOR_BEGIN_REV[] = "-----BEGIN REVIEW-----";
const char ARMOR_END_REV[] = "-----END REVIEW-----";
const char ARMOR_BEGIN_HASH[] = "-----BEGIN SHA512 HASH-----";
const char ARMOR_BEGIN_SIG[] = "-----BEGIN PGP SIGNATURE-----";
const char ARMOR_END_SIG[] = "-----END PGP SIGNATURE-----";
const char ARMOR_BEGIN_REVIEW[] = "=====BEGIN SIGNED REVIEW=====";
const char ARMOR_END_REVIEW[] = "=====END SIGNED REVIEW=====";
const char ARMOR_BEGIN_PUBLIC_KEY[] = "-----BEGIN PGP PUBLIC KEY BLOCK-----";
const char ARMOR_END_PUBLIC_KEY[] = "-----END PGP PUBLIC KEY BLOCK-----";

int Configure()
{
	// Gather data
	char name[256];
	char url[256];
	cout << "\nQuestion 1 of 2\n";
	cout << "Please enter your name (as you would like it to appear on the reviews you make)\n";
	cin.getline(name, 256);
	cout << "\nQuestion 2 of 2\n";
	cout << "Please enter the URL of the web page that displays your public key.\n";
	cout << "(You can generate a web page that you can place at this URL with the\n";
	cout << "command \"" << APP_NAME << " gen-webpage\".)\n";
	cout << "(If you're just tinkering, a bogus URL will be fine for now, but be\n";
	cout << "sure to fix it before you sign any actual reviews.)\n";
	cout << "Example: http://mydomain.com/somepath/pubkey.html\n";
	cin.getline(url, 256);
	if(strncmp(url, "http://", 7) != 0)
		ThrowError("The URL must start with \"http://\". Please run \"%s configure\" again.", APP_NAME);

	// Write the config
	char appFolder[300];
	GetAppFolder(appFolder, 256, APP_NAME);
	strcat(appFolder, "config.txt");
	FILE* pFile = fopen(appFolder, "w");
	FileHolder hFile(pFile);
	fprintf(pFile, "%s%s\n", FIELD_REVIEWER_NAME, name);
	fprintf(pFile, "%s%s\n", FIELD_REVIEWER_URL, url);

	// Thank the user
	cout << "\nThankyou. If you'd like to update these settings, you\n";
	cout << "can run \"" << APP_NAME << " configure\" anytime.\n";
	return 0;
}

int GenerateKey()
{
	SystemExecute("gpg --gen-key", true, NULL, NULL);
	return 0;
}

void GenPubKeyWebPage()
{
	// Export the public key to a temp file
	char pubKeyFile[300];
	GetTempFilename(pubKeyFile);
	GTEMPBUF(char, cmd, strlen(pubKeyFile) + 64);
	sprintf(cmd, "gpg --output %s -a --export", pubKeyFile);
	SystemExecute(cmd, true, NULL, NULL);
	size_t pubKeySize;
	char* pPubKey = LoadFileToBuffer(pubKeyFile, &pubKeySize);
	ArrayHolder<char*> hPubKey(pPubKey);

	// Generate the page
	FILE* pFile = fopen("pubkey.html", "w");
	FileHolder hFile(pFile);
	fprintf(pFile, "<html><head>\n");
	fprintf(pFile, "	<title>Public Key</title>\n");
	fprintf(pFile, "	<link href=\"style.css\" rel=\"stylesheet\" type=\"text/css\">\n");
	fprintf(pFile, "</head><body>\n");
	fprintf(pFile, "<h1>Put Your Name Here</h1>\n");
	fprintf(pFile, "Mention titles and affiliations here. You should include enough information that a visitor will know precisely to whom the public key belongs, but keep it brief. Robots will visit this page more than humans. Feel free to customize this page. You might want to add links to your resume, your homepage, etc.--just make sure the \"extract_pub_key\" and \"extract_works_url\" commands still work with this page after you make significant changes.<br><br>\n");
	fprintf(pFile, "<h2>Link to my publications</h2>\n");
	fprintf(pFile, "<a id=\"works\" href=\"works.html\">My published works</a>\n");
	fprintf(pFile, "<h2>My public key</h2>\n");
	fprintf(pFile, "<code><pre>\n");
	fprintf(pFile, pPubKey);
	fprintf(pFile, "</pre></code>\n");
	fprintf(pFile, "<br><br>\n");

	fprintf(pFile, "</body></html>\n");
}

void GenWorksPage()
{
	FILE* pFile = fopen("works.html", "w");
	FileHolder hFile(pFile);
	fprintf(pFile, "<html><head>\n");
	fprintf(pFile, "	<title>Published Works</title>\n");
	fprintf(pFile, "	<link href=\"style.css\" rel=\"stylesheet\" type=\"text/css\">\n");
	fprintf(pFile, "</head><body>\n");
	fprintf(pFile, "<h1>My published works</h1>\n");
	fprintf(pFile, "Feel free to customize this page to your liking--just make sure the \"extract_reviews\" command still works with this page after you make significant changes.<br><br>\n");
	fprintf(pFile, "<ul>\n");
	fprintf(pFile, "\n");
	fprintf(pFile, "	<li>J. Doe and C. Brown. <a href=\"analysis.pdf\">An Analysis of Practical Systems for Theoretical Analysis.</a> <i>Journal of Practical Theory, vol. 9.</i> 2009. p. 234-250. (<a class=\"reviews\" href=\"reviews_practicaltheory.txt\">Reviews</a>)</li>\n");
	fprintf(pFile, "\n");
	fprintf(pFile, "	<li>J. Doe. <a href=\"mymagnumopus.pdf\"><i>The General Purpose Book of Knowledge.</i></a> 2002. ISBN 123456789. ACME Self-publishing Co. (This is my magnum opus.) (<a class=\"reviews\" href=\"reviews_mybook.txt\">Reviews</a>)</li>\n");
	fprintf(pFile, "\n");
	fprintf(pFile, "	<li>C. Brown and J. Doe. <a href=\"demos.zip\">A collection of interactive demos and miscellaneous applications.</a> (I use these demos in my public presentations.) (<a class=\"reviews\" href=\"reviews_demos.txt\">Reviews</a>)</li>\n");
	fprintf(pFile, "\n");
	fprintf(pFile, "	<li>J. Doe. <a href=\"lecturenotes.odp\">Lecture Notes on Systematic Design.</a> (My course lectures.) (<a class=\"reviews\" href=\"reviews_lecturenotes.txt\">Reviews</a>)</li>\n");
	fprintf(pFile, "\n");
	fprintf(pFile, "</ul>\n");
	fprintf(pFile, "</body></html>\n");
}

void GenStylePage()
{
	FILE* pFile = fopen("style.css", "w");
	FileHolder hFile(pFile);
	fprintf(pFile, "body,td,center {\n");
	fprintf(pFile, "	font-family: verdana, tahoma, geneva, sans-serif;\n");
	fprintf(pFile, "	font-size: 18px;\n");
	fprintf(pFile, "}\n");
	fprintf(pFile, "\n");
	fprintf(pFile, "a {\n");
	fprintf(pFile, "	text-decoration: none;\n");
	fprintf(pFile, "}\n");
	fprintf(pFile, "\n");
	fprintf(pFile, "a:hover {\n");
	fprintf(pFile, "	text-decoration: underline;\n");
	fprintf(pFile, "}\n");
	fprintf(pFile, "\n");
	fprintf(pFile, "li {\n");
	fprintf(pFile, "	padding:10px;\n");
	fprintf(pFile, "	list-style-type:none;\n");
	fprintf(pFile, "	margin-left:20px;\n");
	fprintf(pFile, "	text-indent:-20px;\n");
	fprintf(pFile, "}\n");
	fprintf(pFile, "\n");
	fprintf(pFile, ".reviews {\n");
	fprintf(pFile, "	color: #008888;\n");
	fprintf(pFile, "}\n");
}

int GenWebPage()
{
#ifdef WIN32
	mkdir("web");
#else
	mkdir("web", S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); // read/write/search permissions for owner and group, and with read/search permissions for others
#endif
	chdir("web");
	GenPubKeyWebPage();
	GenWorksPage();
	GenStylePage();
	return 0;
}

int GenerateTemplate()
{
	if(access("review.txt", 0) == 0)
		ThrowError("The file \"review.txt\" already exists.");

	FILE* pFile = fopen("review.txt", "w");
	FileHolder hFile(pFile);
	fprintf(pFile, "reviewer-confidence: 7\n");
	fprintf(pFile, "overall-quality: 6.5\n");
	fprintf(pFile, "significance: 5.8\n");
	fprintf(pFile, "originality: 10\n");
	fprintf(pFile, "methodology: 9\n");
	fprintf(pFile, "clarity: 10\n");
	fprintf(pFile, "succinctness: 4\n");
	fprintf(pFile, "[Change the scores. Remove summary meta-scores that aren't relevant. (Don't give meaningless scores.) If you add additional meta-scores, try to use the same meta-scores as other reviews in your field. All scores range from 0 to 10. You may use fractional scores if desired.]\n");
	fprintf(pFile, "[General rubrick for all scores: 2=typical of an intelligent novice in the field. 5=typical of one knowledgable and skilled in the field. 8=typical of prominent experts in the field. (Remember that inflated reviews of poor works are an insult to those that work hard to create works worthy of such reviews. You can't please everyone and give good reviews too.)]\n");
	fprintf(pFile,  "[Remove all of the instructions in brackets.]\n");
	fprintf(pFile, "\n");
	fprintf(pFile, "I am qualified to review this work because [Mention a few salient qualifications here. Don't mention your entire resume. You are not the one being reviewed.] My ability to review this work is limited because [Be honest, it promotes credibility. Mention sections you didn't fully understand, parts you didn't have time to consider deeply, possible conflicts of interest, etc.].\n");
	fprintf(pFile, "\n");
	fprintf(pFile, "This work involves [Give a very brief summary of the major contributions of the work]. My overall reaction is [Insert reaction here]. [Now give a detailed review of the work (not the author). Your audience is not the author, it is people the author would like to impress. Suggestions for improvement should be communicated to the author separately. Don't press Enter until the end of a paragraph--your text editor should do the line wrapping.]\n");

	cout << "\nThe file \"review.txt\" has been generated. (It is better to obtain\n";
	cout << "a template from other researchers in your field, but if you cannot find\n";
	cout << "one, you may edit review.txt to make your review template.)\n";
	return 0;
}

void DigestFile(FILE* pOutStream, const char* filename)
{
	unsigned char buf[64];
	Sha512DigestFile(buf, filename);

	// Write the digest
	fprintf(pOutStream, "-----BEGIN SHA512 HASH-----\n");
	BufferToHexBigEndian(pOutStream, buf, 32);
	fprintf(pOutStream, "\n");
	BufferToHexBigEndian(pOutStream, buf + 32, 32);
	fprintf(pOutStream, "\n");
	fprintf(pOutStream, "-----END SHA512 HASH-----\n");
}

int SignReview(const char* reviewFilename, const char* workFilename)
{
	// Load the config file
	char configFilename[300];
	GetAppFolder(configFilename, 256, APP_NAME);
	strcat(configFilename, "config.txt");
	if(access(configFilename, 0) != 0)
		ThrowError("You must run \"%s config\" before you can sign reviews.", APP_NAME);
	size_t configLen;
	char* config;
	{
		size_t configLenRaw;
		char* configRaw = LoadFileToBuffer(configFilename, &configLenRaw);
		ArrayHolder<char*> hConfigRaw(configRaw);
		config = NormalizeTextFile(configRaw, configLenRaw, &configLen);
	}
	ArrayHolder<char*> hConfig(config);

	// Load the review
	size_t reviewLen;
	char* pReview;
	{
		size_t reviewLenRaw;
		char* pReviewRaw = LoadFileToBuffer(reviewFilename, &reviewLenRaw);
		ArrayHolder<char*> hReviewRaw(pReviewRaw);
		pReview = NormalizeTextFile(pReviewRaw, reviewLenRaw, &reviewLen);
	}
	ArrayHolder<char*> hReview(pReview);

	// Warn reviewers before they sign a sketchy document format
	PathData pd;
	ParseFilename(reviewFilename, &pd);
	if(stricmp(reviewFilename + pd.extStart, ".doc") == 0)
	{
		cout << "WARNING! You are about to put your signature on a proprietary document format!\n";
		cout << "This would prevent the author from changing the format of the document and\n";
		cout << "would prevent readers from accessing it without first purchasing non-free\n";
		cout << "software. Further, this format may not be supported in the future, rendering\n";
		cout << "the document completely unreadable. It is strongly recommended that you ask\n";
		cout << "the author to convert it to PDF format before you sign it. Are you sure you want\n";
		cout << "to ignore this advice and proceed anyway? [y/N]\n";
		char c;
		cin >> c;
		if(c != 'y' && c != 'Y')
			ThrowError("User aborted");
	}
	else if(stricmp(reviewFilename + pd.extStart, ".docx") == 0)
	{
		cout << "WARNING! You are about to put your signature on a \".docx\" file! It is\n";
		cout << "recommended that you ask the author to convert it to a PDF document\n";
		cout << "before you sign it. Are you sure you want to ignore this advice and\n";
		cout << "proceed anyway? [y/N]\n";
		char c;
		cin >> c;
		if(c != 'y' && c != 'Y')
			ThrowError("User aborted");
	}

	// Put all the pieces together
	char tempFile[300];
	GetTempFilename(tempFile);
	FILE* pSignMe = fopen(tempFile, "wb");
	FileHolder hSignMe(pSignMe);
	fprintf(pSignMe, "%s\n", ARMOR_BEGIN_REV);
	PathData pd2;
	ParseFilename(workFilename, &pd2);
	fprintf(pSignMe, "%s%s\n", FIELD_FILENAME, workFilename + pd2.fileStart);

	// Print the data
	time_t t = time((time_t*)0);
#ifdef WIN32
	struct tm thetime;
	localtime_s(&thetime, &t);
	struct tm* pTime = &thetime;
#else
	struct tm* pTime = localtime(&t);
#endif
	fprintf(pSignMe, "%s%d-%02d-%02d\n", FIELD_DATE, pTime->tm_year + 1900, pTime->tm_mon + 1, pTime->tm_mday);

	fprintf(pSignMe, config);
	fprintf(pSignMe, pReview);
	fprintf(pSignMe, "%s\n", ARMOR_END_REV);
	DigestFile(pSignMe, workFilename);
	hSignMe.Close();
	size_t revLen;
	char* pRev = LoadFileToBuffer(tempFile, &revLen);
	ArrayHolder<char*> hRev(pRev);

	// Sign it
	char sigFile[300];
	GetTempFilename(sigFile);
	{
		GTEMPBUF(char, buf, strlen(sigFile) + strlen(tempFile) + 64);
		sprintf(buf, "gpg --output %s -abs %s", sigFile, tempFile);
		SystemExecute(buf, true, NULL, NULL);
	}
	size_t sigLen;
	char* pSig;
	{
		size_t sigLenRaw;
		char* sigRaw = LoadFileToBuffer(sigFile, &sigLenRaw);
		ArrayHolder<char*> hSigRaw(sigRaw);
		pSig = NormalizeTextFile(sigRaw, sigLenRaw, &sigLen);
	}
	ArrayHolder<char*> hSig(pSig);

	// Open the output file
	GTEMPBUF(char, targetFilename, pd.len + 16);
	memcpy(targetFilename, reviewFilename, pd.extStart);
	strcpy(targetFilename + pd.extStart, ".signed");
	strcpy(targetFilename + pd.extStart + 7, reviewFilename + pd.extStart);
	if(access(targetFilename, 0) == 0)
		ThrowError("The file \"%s\" already exists.", targetFilename);
	FILE* pTargetFile = fopen(targetFilename, "w");
	FileHolder hTargetFile(pTargetFile);

	// Generate the signed review
	fprintf(pTargetFile, "=====BEGIN SIGNED REVIEW=====\n");
	fprintf(pTargetFile, pRev);
	fprintf(pTargetFile, pSig);
	fprintf(pTargetFile, "=====END SIGNED REVIEW=====\n");

	// Clean up
	DeleteFile(tempFile);
	DeleteFile(sigFile);
	cout << "\nDone. The signed review has been saved as " << targetFilename << "\n";
	return 0;
}

void FindPublicKey(const char* pPage, int* begPubKey, int* endPubKey)
{
	*begPubKey = 0;
	*endPubKey = 0;
	int pos = 0;
	while(pPage[pos] != '\0')
	{
		if(strncmp(pPage + pos, ARMOR_BEGIN_PUBLIC_KEY, sizeof(ARMOR_BEGIN_PUBLIC_KEY) - 1) == 0)
		{
			if(*begPubKey != 0)
				ThrowError("Multiple public key sections");
			*begPubKey = pos;
		}
		else if(strncmp(pPage + pos, ARMOR_END_PUBLIC_KEY, sizeof(ARMOR_END_PUBLIC_KEY) - 1) == 0)
		{
			if(*begPubKey == 0 || *endPubKey != 0)
				ThrowError("Parse error in public key block");
			*endPubKey = pos;
		}
		while(pPage[pos] != '\0' && pPage[pos] != '\n' && pPage[pos] != '\r')
			pos++;
		while(pPage[pos] == '\n' || pPage[pos] == '\r')
			pos++;
	}

	// Make sure we found all of the required parts
	if(*begPubKey == 0)
		ThrowError("public key not found");
}

void ParseReview(const char* pReview, int* url, int* fn, int* begHash, int* begPgpSig, int* endPgpSig, int* endReview)
{
	*url = 0;
	*fn = 0;
	*begHash = 0;
	*begPgpSig = 0;
	*endPgpSig = 0;
	*endReview = 0;
	int pos = 29;
	while(pReview[pos] != '\0')
	{
		if(strncmp(pReview + pos, FIELD_REVIEWER_URL, sizeof(FIELD_REVIEWER_URL) - 1) == 0)
		{
			if(*url != 0)
				ThrowError("Multiple occurrences of %s", FIELD_REVIEWER_URL);
			*url = pos;
		}
		else if(strncmp(pReview + pos, FIELD_FILENAME, sizeof(FIELD_FILENAME) - 1) == 0)
		{
			if(*fn != 0)
				ThrowError("Multiple occurrences of %s", FIELD_FILENAME);
			*fn = pos;
		}
		else if(strncmp(pReview + pos, ARMOR_BEGIN_HASH, sizeof(ARMOR_BEGIN_HASH) - 1) == 0)
		{
			if(*fn == 0 || *url == 0)
				ThrowError("Expected the filename and url to be specified before the hash section");
			if(*begHash != 0)
				ThrowError("Multiple hash sections");
			*begHash = pos;
		}
		else if(strncmp(pReview + pos, ARMOR_BEGIN_SIG, sizeof(ARMOR_BEGIN_SIG) - 1) == 0)
		{
			if(*begHash == 0)
				ThrowError("The hash section must precede the signature");
			if(*begPgpSig != 0)
				ThrowError("Multiple signature sections");
			*begPgpSig = pos;
		}
		else if(strncmp(pReview + pos, ARMOR_END_SIG, sizeof(ARMOR_END_SIG) - 1) == 0)
		{
			if(*begPgpSig == 0)
				ThrowError("The begin must precede the end of the signature section");
			if(*endPgpSig != 0)
				ThrowError("Multiple end signatures");
			*endPgpSig = pos;
		}
		else if(strncmp(pReview + pos, ARMOR_END_REVIEW, sizeof(ARMOR_END_REVIEW) - 1) == 0)
		{
				if(*endPgpSig == 0)
					ThrowError("Unterminated end signature");
				if(*endReview != 0)
					ThrowError("Multiple ends of review");
				*endReview = pos;
				break;
		}
		while(pReview[pos] != '\0' && pReview[pos] != '\n' && pReview[pos] != '\r')
			pos++;
		while(pReview[pos] == '\n' || pReview[pos] == '\r')
			pos++;
	}

	// Make sure we found all of the required parts
	if(*endReview == 0)
		ThrowError("Unterminated review");

	// Make sure the opening armor is on its own line
	if(pReview[29] != '\r' && pReview[29] != '\n')
		ThrowError("Armor problem");

	// Make sure the closing armor is on its own line
	if(pReview[*endReview + 27] != '\r' && pReview[*endReview + 27] != '\n')
		ThrowError("Armor problem");

	// Make sure the end of review immediately follows the PGP sig
	int endLineLen = 1;
	if(pReview[*endPgpSig + sizeof(ARMOR_END_REVIEW) - 1] == '\r')
	{
		if(pReview[*endPgpSig + sizeof(ARMOR_END_REVIEW)] == '\n')
			endLineLen = 2;
		else
			endLineLen = 1;
	}
	else if(pReview[*endPgpSig + sizeof(ARMOR_END_REVIEW) - 1] == '\n')
		endLineLen = 1;
	else
		ThrowError("Armor problem");
	if((int)*endReview != (int)(*endPgpSig + sizeof(ARMOR_END_REVIEW) - 1 + endLineLen))
		ThrowError("Armor problem");
}

unsigned char* WebGetToMem(const char* szUrl, int* size)
{
	// Download the file
	GHttpClient client;
	if(!client.Get(szUrl, true))
		ThrowError("Could not get the url %s", szUrl);
	float progress;
	while(client.CheckStatus(&progress) == GHttpClient::Downloading)
		GSpinLock::sleep(100);
	if(client.CheckStatus(&progress) == GHttpClient::NotFound)
		ThrowError("Url not found: %s", szUrl);
	if(client.CheckStatus(&progress) != GHttpClient::Done)
		ThrowError("Error trying to download url: %s", szUrl);
	return client.DropData(size);
}

int WebGet(const char* szUrl, const char* szTargetFilename)
{
	int size;
	unsigned char* pData = WebGetToMem(szUrl, &size);
	Holder<unsigned char*> hData(pData);
	FILE* pFile = fopen(szTargetFilename, "wb");
	FileHolder hFile(pFile);
	if(fwrite(pData, size, 1, pFile) != 1)
		ThrowError("error writing to file \"%s\"", szTargetFilename);
	return 0;
}

void RetrievePublicKey(const char* pUrl)
{
	// Extract the url
	int urlLen = strlinelen(pUrl);
	GTEMPBUF(char, szUrl, urlLen + 1);
	memcpy(szUrl, pUrl, urlLen);
	szUrl[urlLen] = '\0';

	// Download the public key page
	char pubKeyPageFilename[300];
	GetTempFilename(pubKeyPageFilename);
	try
	{
		WebGet(szUrl, pubKeyPageFilename);
	}
	catch(const char* szError)
	{
		ThrowError("Failed to download the public key from %s due to: %s", szUrl, szError);
	}
	catch(...)
	{
		ThrowError("A mysterious error occurred while attempting to download the public key from %s.", szUrl);
	}

	// Parse the public key page
	int size, begPubKey, endPubKey;
	unsigned char* pPage = WebGetToMem(szUrl, &size);
	Holder<unsigned char*> hPage(pPage);
	FindPublicKey((const char*)pPage, &begPubKey, &endPubKey);

	// Save to a temp file
	char pubKeyFile[300];
	GetTempFilename(pubKeyFile);
	{
		FILE* pFile = fopen(pubKeyFile, "wb");
		FileHolder hFile(pFile);
		if(fwrite(pPage + begPubKey, endPubKey - begPubKey + sizeof(ARMOR_END_PUBLIC_KEY) - 1, 1, pFile) != 1)
			ThrowError("Downloaded the public key, but failed to write it to the file \"%s\". (Is this a permissions issue? Or perhaps the disk is full?)", pubKeyFile);
	}

	// Import the public key to our list of known keys
	GTEMPBUF(char, cmd, strlen(pubKeyFile) + 64);
	sprintf(cmd, "gpg --import %s", pubKeyFile);
	SystemExecute(cmd, true, NULL, NULL);
}

void CheckSignature(const char* pReview, int reviewLen, const char* pSig, int sigLen, const char* pUrl)
{
	// Copy review portion to a temp file
	char tempReview[300];
	GetTempFilename(tempReview);
	{
		size_t reviewNormalizedLen;
		char* reviewNormalized = NormalizeTextFile(pReview, reviewLen, &reviewNormalizedLen);
		ArrayHolder<char*> hReviewNormalized(reviewNormalized);
		FILE* pFile = fopen(tempReview, "wb");
		FileHolder hFile(pFile);
		if(fwrite(reviewNormalized, reviewNormalizedLen, 1, pFile) != 1)
			ThrowError("error writing to file \"%s\"", tempReview);
	}

	// Copy signature portion to a temp file
	char tempSignature[308];
	strcpy(tempSignature, tempReview);
	strcat(tempSignature, ".asc");
	{
		size_t sigNormalizedLen;
		char* sigNormalized = NormalizeTextFile(pSig, sigLen, &sigNormalizedLen);
		ArrayHolder<char*> hSigNormalized(sigNormalized);
		FILE* pFile = fopen(tempSignature, "wb");
		FileHolder hFile(pFile);
		if(fwrite(sigNormalized, sigNormalizedLen, 1, pFile) != 1)
			ThrowError("error writing to file \"%s\"", tempSignature);
	}

	// Use GnuPG to verify it
	char stderrFilename[300];
	GetTempFilename(stderrFilename);
	GTEMPBUF(char, buf, strlen(tempSignature) + 64);
	sprintf(buf, "gpg --verify %s", tempSignature);
	int ret = SystemExecute(buf, true, NULL, stderrFilename);
	if(ret != 0)
	{
		size_t stderrLen;
		char* pStdErr = LoadFileToBuffer(stderrFilename, &stderrLen);
		ArrayHolder<char*> hStdErr(pStdErr);

		if(strstr(pStdErr, "public key not found") != NULL)
		{
			if(pUrl)
			{
				try
				{
					RetrievePublicKey(pUrl);
				}
				catch(const char* szError)
				{
					ThrowError("The signature cannot be verified without the signer's public key, and the signer's public key could not be retrieved because: %s", szError);
				}
				catch(...)
				{
					ThrowError("The signature cannot be verified without the signer's public key, and a mysterious error occurred while attempting to obtain the signer's public key.");
				}
				CheckSignature(pReview, reviewLen, pSig, sigLen, NULL/*don't retrieve the key again*/);
			}
			else
				ThrowError("The public key published at the signer's URL is not the right key to verify this signature.");
		}

		cout << pStdErr << "\n";
		ThrowError("BAD signature!");
	}
	DeleteFile(stderrFilename);

	// Clean up
	DeleteFile(tempSignature);
	DeleteFile(tempReview);
}

void CheckFileHash(const char* pFilename, const char* pHash, int hashLen)
{
	// Extract the filename
	int fnLen = strlinelen(pFilename);
	GTEMPBUF(char, szFilename, fnLen + 1);
	memcpy(szFilename, pFilename, fnLen);
	szFilename[fnLen] = '\0';

	// Hash the file
	if(access(szFilename, 0) != 0)
		ThrowError("The file \"%s\" is not found, so the hash cannot be checked", szFilename);
	unsigned char fileHash[64];
	Sha512DigestFile(fileHash, szFilename);

	// Extract the review hash
	unsigned char reviewHash[64];
	pHash += 28;
	if(*pHash == '\n')
		pHash++;
	int i;
	for(i = 0; i < 32; i++)
	{
		reviewHash[i] = HexToByte(pHash[0], pHash[1]);
		pHash += 2;
	}
	pHash++;
	if(*pHash == '\n')
		pHash++;
	for( ; i < 64; i++)
	{
		reviewHash[i] = HexToByte(pHash[0], pHash[1]);
		pHash += 2;
	}

	// Compare the hashes
	if(memcmp(reviewHash, fileHash, 64) != 0)
		ThrowError("The file hash does not match the hash in the review!");
}

void VerifyReview(const char* pReview)
{
	// Check the armor
	int url, fn, begHash, begPgpSig, endPgpSig, endReview;
	ParseReview(pReview, &url, &fn, &begHash, &begPgpSig, &endPgpSig, &endReview);
	int startReview = sizeof(ARMOR_BEGIN_REVIEW);
	if(pReview[startReview] == '\n' && pReview[startReview - 1] == '\r')
		startReview++;

	// Check the signature
	CheckSignature(pReview + startReview, begPgpSig - startReview, pReview + begPgpSig, endReview - begPgpSig, pReview + url + sizeof(FIELD_REVIEWER_URL) - 1);
	cout << "Good signature\n";

	// Check the file hash
	CheckFileHash(pReview + fn + sizeof(FIELD_FILENAME) - 1, pReview + begHash, begPgpSig - begHash);
	cout << "Good file hash\n";
}

int VerifySingleReview(const char* pReviews, GIntArray* pReviewStarts, int index)
{
	int ret = 0;
	try
	{
		cout << "-----Checking review " << index << ", which begins on line " << pReviewStarts->GetInt(2 * index + 1) << "-----\n";
		VerifyReview(pReviews + pReviewStarts->GetInt(2 * index));
	}
	catch(const char* szError)
	{
		ret = 1;
		cout << "Invalid review: " << szError << "\n";
	}
	catch(...)
	{
		ret = 1;
		cout << "A mysterious error occurred while trying to verify this review!\n";
	}
	return ret;
}

// Fills pReviewStarts with the starting position and line
// numbers of each review in pReviews
void FindReviews(GIntArray* pReviewStarts, char* pReviews)
{
	// Find the reviews
	int pos = 0;
	int liner = 1;
	int linen = 1;
	while(true)
	{
		// Find the start of the next review
		while(pReviews[pos] != '\0')
		{
			if(strncmp(pReviews + pos, ARMOR_BEGIN_REVIEW, sizeof(ARMOR_BEGIN_REVIEW) - 1) == 0)
				break;
			while(pReviews[pos] != '\0' && pReviews[pos] != '\n' && pReviews[pos] != '\r')
				pos++;
			while(pReviews[pos] == '\n' || pReviews[pos] == '\r')
			{
				if(pReviews[pos] == '\n')
					linen++;
				else
					liner++;
				pos++;
			}
		}
		if(pReviews[pos] == '\0')
			break;
		pReviewStarts->AddInt(pos);
		pReviewStarts->AddInt(MAX(liner, linen));
		pos += sizeof(ARMOR_BEGIN_REVIEW) - 1;
	}
}

int CountReviews(const char* reviewsFile)
{
	// Load the reviews file
	size_t reviewsLen;
	char* pReviews = LoadFileToBuffer(reviewsFile, &reviewsLen);
	ArrayHolder<char*> hReviews(pReviews);

	// Find the reviews
	GIntArray starts(16);
	FindReviews(&starts, pReviews);
	cout << starts.GetSize() / 2 << "\n";
	return 0;
}

int Verify(const char* reviewsFile, const char* reviewIndex)
{
	// Load the reviews file
	size_t reviewsLen;
	char* pReviews = LoadFileToBuffer(reviewsFile, &reviewsLen);
	ArrayHolder<char*> hReviews(pReviews);

	// Find the reviews
	GIntArray starts(16);
	FindReviews(&starts, pReviews);

	// Verify them
	int ret = 0;
	int i = atoi(reviewIndex);
	if(i > 0)
		ret = VerifySingleReview(pReviews, &starts, i - 1);
	else
	{
		if(starts.GetSize() < 1)
			ThrowError("There are no signed reviews in that file.");
		for(i = 0; i < starts.GetSize() / 2; i++)
			ret = MAX(ret, VerifySingleReview(pReviews, &starts, i));
		cout << "-----Total signed reviews: " << starts.GetSize() / 2 << "-----\n";
	}
	return ret;
}

int ExtractPublicKey(const char* htmlFile)
{
	// Load the html file
	size_t fileLen;
	char* pFile = LoadFileToBuffer(htmlFile, &fileLen);
	ArrayHolder<char*> hFile(pFile);

	// Find the public key
	int beg, end;
	FindPublicKey(pFile, &beg, &end);
	int len = end - beg + sizeof(ARMOR_END_PUBLIC_KEY) - 1;
	GTEMPBUF(char, pubKey, len + 1);
	memcpy(pubKey, pFile + beg, len);
	pubKey[len] = '\0';
	printf("%s\n", pubKey);
	return 0;
}

class LinkExtractor : public GHtml
{
protected:
	bool m_gotRightProp;
	const char* m_url;
	int m_urlLen;
	const char* m_szPropName;
	int m_propNameLen;
	const char* m_szPropVal;
	int m_propValLen;

public:
	LinkExtractor(const char* pDoc, int size, const char* szPropName, const char* szPropVal)
	: GHtml(pDoc, size)
	{
		m_gotRightProp = false;
		m_url = NULL;
		m_urlLen = 0;
		m_szPropName = szPropName;
		m_propNameLen = strlen(szPropName);
		m_szPropVal = szPropVal;
		m_propValLen = strlen(szPropVal);
	}

	virtual ~LinkExtractor()
	{
	}

	virtual void OnTag(const char* pTagName, int tagNameLen)
	{
		m_gotRightProp = false;
		m_url = NULL;
	}

	void GotOne()
	{
		GTEMPBUF(char, buf, m_urlLen + 1);
		memcpy(buf, m_url, m_urlLen);
		buf[m_urlLen] = '\0';
		printf("%s\n", buf);
		m_gotRightProp = false;
		m_url = NULL;
	}

	virtual void OnTagParam(const char* pTagName, int nTagNameLen, const char* pParamName, int nParamNameLen, const char* pValue, int nValueLen)
	{
		if(nTagNameLen != 1 || strnicmp(pTagName, "a", 1) != 0)
			return;
		if(nParamNameLen == m_propNameLen && strnicmp(pParamName, m_szPropName, nParamNameLen) == 0)
		{
			if(nValueLen == m_propValLen && strnicmp(pValue, m_szPropVal, nValueLen) == 0)
			{
				m_gotRightProp = true;
				if(m_url)
					GotOne();
			}
		}
		else if(nParamNameLen == 4 && strnicmp(pParamName, "href", 4) == 0)
		{
			m_url = pValue;
			m_urlLen = nValueLen;
			if(m_gotRightProp)
				GotOne();
		}
	}
};

int ExtractWorksUrl(const char* htmlFile)
{
	// Load the html file
	size_t fileLen;
	char* pFile = LoadFileToBuffer(htmlFile, &fileLen);
	ArrayHolder<char*> hFile(pFile);

	// Print matching urls
	LinkExtractor finder(pFile, strlen(pFile), "id", "works");
	while(true)
	{
		if(!finder.ParseSomeMore())
			break;
	}
	return 0;
}

int ExtractReviewsUrls(const char* htmlFile)
{
	// Load the html file
	size_t fileLen;
	char* pFile = LoadFileToBuffer(htmlFile, &fileLen);
	ArrayHolder<char*> hFile(pFile);

	// Print matching urls
	LinkExtractor finder(pFile, strlen(pFile), "class", "reviews");
	while(true)
	{
		if(!finder.ParseSomeMore())
			break;
	}
	return 0;
}

int ExtractField(const char* reviewsFile, const char* reviewIndex, const char* fieldName)
{
	// Load the reviews file
	size_t reviewsLen;
	char* pReviews = LoadFileToBuffer(reviewsFile, &reviewsLen);
	ArrayHolder<char*> hReviews(pReviews);

	// Find the reviews
	GIntArray starts(16);
	FindReviews(&starts, pReviews);
	int index = atoi(reviewIndex) - 1;
	if(index < 0 || index >= starts.GetSize() / 2)
		ThrowError("The review index must be from 1 to %d.", starts.GetSize() / 2);

	// Parse the major sections of the review
	const char* pReview = pReviews + starts.GetInt(2 * index);
	int url, fn, begHash, begPgpSig, endPgpSig, endReview;
	ParseReview(pReview, &url, &fn, &begHash, &begPgpSig, &endPgpSig, &endReview);
	int startReview = sizeof(ARMOR_BEGIN_REVIEW);
	if(pReview[startReview] == '\n' && pReview[startReview - 1] == '\r')
		startReview++;

	// Find the specified field
	int fieldLen = strlen(fieldName);
	int pos = startReview;
	while(pReview[pos] != '\0' && pos < begHash)
	{
		if(strncmp(pReview + pos, fieldName, fieldLen) == 0 && pReview[pos + fieldLen] == ':')
		{
			// Extract the value
			int valStart = pos + fieldLen + 1;
			while(pReview[valStart] == ' ' || pReview[valStart] == '\t')
				valStart++;
			int valLen = strlinelen(pReview + valStart);
			GTEMPBUF(char, szVal, valLen + 1);
			memcpy(szVal, pReview + valStart, valLen);
			szVal[valLen] = '\0';
			printf("%s\n", szVal);
			return 0;
		}
		while(pReview[pos] != '\0' && pReview[pos] != '\n' && pReview[pos] != '\r')
			pos++;
		while(pReview[pos] == '\n' || pReview[pos] == '\r')
			pos++;
	}

	// Handle not-found case
	printf("<Value not found>\n");
	return 2;
}

int ShowUsage()
{
	cout << "\n";
	cout << "GPeerReview, a tool for helping peers to review and sign each\n";
	cout << "             others' works. By: Mike Gashler\n";
	cout << "\n";
	cout << "Most common commands: (If you're new here, you probably want to do\n";
	cout << "                       each of these commands in this order.)\n";
	cout << "\n";
	cout << "    " << APP_NAME << " gen-key\n";
	cout << "        Generate your key-pair. (You need a key to sign reviews.\n";
	cout << "        This command is the same as doing \"gpg --gen-key\". In\n";
	cout << "        fact, many of these commands are more or less just thin\n";
	cout << "        wrappers around gpg, which does all of the heavy lifting.)\n";
	cout << "\n";
	cout << "    " << APP_NAME << " gen-webpage\n";
	cout << "        Generates a web page to display your public key. (Of\n";
	cout << "        course you should put this page on the world-wide-web, and\n";
	char configFilename[300];
	GetAppFolder(configFilename, 256, APP_NAME);
	strcat(configFilename, "config.txt");
	cout << "        make sure the " << configFilename << " file\n";
	cout << "        contains the correct URL for this page so that the reviews\n";
	cout << "        you make will reference it.)\n";
	cout << "\n";
	cout << "    " << APP_NAME << " config\n";
	cout << "        Runs a little wizard to help you configure the\n";
	cout << "        " << configFilename << " file.\n";
	cout << "\n";
	cout << "    " << APP_NAME << " gen-template\n";
	cout << "        Generates a template review that you can use when writing\n";
	cout << "        a review of another person's work. (It is better to\n";
	cout << "        obtain a template from someone in your field, so that it\n";
	cout << "        will contain salient features, but if you can't find one,\n";
	cout << "        use this command.)\n";
	cout << "\n";
	cout << "    " << APP_NAME << " sign [review_file] [file]\n";
	cout << "        Digitally signs [review_file]. (Well, first, it makes a\n";
	cout << "        hash of [file] and adds it to the review. It also inserts\n";
	cout << "        the reviewer name, URL, and date into the review, then it\n";
	cout << "        signs the review with the reviewer's private key.)\n";
	cout << "\n";
	cout << "    " << APP_NAME << " verify [reviews_file]\n";
	cout << "        Verifies all of the signed reviews in the specified file.\n";
	cout << "        [reviews_file] should be a text file that contains one or\n";
	cout << "        more reviews. (It's common to put all of the reviews for\n";
	cout << "        a single work into one file.)\n";
	cout << "\n";
	cout << "Advanced (and somewhat obscure) commands:\n";
	cout << "    " << APP_NAME << " wget [url] [target_filename]\n";
	cout << "        Downloads a single file from the web.\n";
	cout << "        (There are better tools for doing this, but it may be\n";
	cout << "        difficult to find them on *certain* operating systems, so\n";
	cout << "        this feature is provided for the convenience of those\n";
	cout << "        that may want to crawl the web/network of reviews.)\n";
	cout << "\n";
	cout << "    " << APP_NAME << " extract_pub_key [html_file]\n";
	cout << "        Extracts the public key from the specified html file and\n";
	cout << "        prints it to stdout.\n";
	cout << "\n";
	cout << "    " << APP_NAME << " extract_works_url [html_file]\n";
	cout << "        Extracts the url of the works page from the specified\n";
	cout << "        html file and prints it to stdout.\n";
	cout << "\n";
	cout << "    " << APP_NAME << " extract_reviews_urls [html_file]\n";
	cout << "        Extracts the urls of the reviews files from the specified\n";
	cout << "        html file and prints it to stdout.\n";
	cout << "\n";
	cout << "    " << APP_NAME << " count-reviews [reviews_file]\n";
	cout << "        Counts the number of reviews in [reviews_file] and prints\n";
	cout << "        the result to stdout.\n";
	cout << "\n";
	cout << "    " << APP_NAME << " verify [reviews_file] [review_index]\n";
	cout << "        Verifies only the review specified by [review_index] in\n";
	cout << "        [reviews_file], where 0 < [review_index] <= count-reviews\n";
	cout << "\n";
	cout << "    " << APP_NAME << " extract_field [reviews_file] [review_index] [field_name]\n";
	cout << "        Extracts the value of the field specified by \"field_name\"\n";
	cout << "        from the review specified by \"reviews_file\" and \"review_index\"\n";
	cout << "        and prints it to stdout.\n";
	cout << "\n";
	return 1;
}

int DoIt(int argc, char *argv[])
{
	if(argc < 2)
		return ShowUsage();
	else if(stricmp(argv[1], "config") == 0 && argc == 2)
		return Configure();
	else if(stricmp(argv[1], "gen-key") == 0 && argc == 2)
		return GenerateKey();
	else if(stricmp(argv[1], "gen-webpage") == 0 && argc == 2)
		return GenWebPage();
	else if(stricmp(argv[1], "gen-template") == 0 && argc == 2)
		return GenerateTemplate();
	else if(stricmp(argv[1], "sign") == 0 && argc == 4)
		return SignReview(argv[2], argv[3]);
	else if(stricmp(argv[1], "verify") == 0 && argc == 3)
		return Verify(argv[2], "0");
	else if(stricmp(argv[1], "verify") == 0 && argc == 4)
		return Verify(argv[2], argv[3]);
	else if(stricmp(argv[1], "wget") == 0 && argc == 4)
		return WebGet(argv[2], argv[3]);
	else if(stricmp(argv[1], "count-reviews") == 0 && argc == 3)
		return CountReviews(argv[2]);
	else if(stricmp(argv[1], "extract_pub_key") == 0 && argc == 3)
		return ExtractPublicKey(argv[2]);
	else if(stricmp(argv[1], "extract_works_url") == 0 && argc == 3)
		return ExtractWorksUrl(argv[2]);
	else if(stricmp(argv[1], "extract_reviews_urls") == 0 && argc == 3)
		return ExtractReviewsUrls(argv[2]);
	else if(stricmp(argv[1], "extract_field") == 0 && argc == 5)
		return ExtractField(argv[2], argv[3], argv[4]);
	else
		return ShowUsage();
}

void onSigSegV(int n)
{
	throw "A memory access violation occurred";
}

void onSigInt(int n)
{
	throw "The program was interrupted with SIGINT";
}

void onSigFpe(int n)
{
	throw "A floating point error occurred";
}

void TurnSegFaultsIntoExceptions()
{
#ifndef WIN32
	signal(SIGSEGV, onSigSegV);
	signal(SIGINT, onSigInt);
	signal(SIGFPE, onSigFpe);
	//signal(SIGPIPE, onSigPipe);
	//signal(SIGQUIT, onSigQuit);
	//signal(SIGTSTP, onSigTstp);
	//signal(SIGABRT, onSigAbrt);
#endif // !WIN32
}

int main(int argc, char *argv[])
{
	TurnSegFaultsIntoExceptions();
	int nRet = 0;
	try
	{
		DoIt(argc, argv);
	}
	catch(const char* szError)
	{
		fprintf(stderr, "%s\n", szError);
		nRet = 1;
	}
	catch(...)
	{
		fprintf(stderr, "An exception of unknown type was thrown!");
		nRet = 1;
	}

	cout.flush();
	return nRet;
}

